﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Text.RegularExpressions;

using NetFtp.Core.Utils;

namespace NetFtp.Core.Ftp
{
    /// <summary>
    /// Permet d'envoyer et de récupérer des fichier à partir d'un serveur FTP.
    /// </summary>
    public class FtpOperation
    {

        public enum ListDirectoryKind
        {
            FILE_ONLY,
            DIRECTORY_ONLY,
            FILE_AND_DIRECTORY
        }

        /// <summary>
        /// Effectively uploads the File described by the path to the dest Uri.
        /// </summary>
        /// <param name="source">pathname of the file to be uploaded</param>
        /// <param name="dest"></param>
        public static void DoUpload(FtpLogin ftpInfo, string path, Uri dest, bool passive)
        {
            if (dest.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(dest) as FtpWebRequest;


                //Console.WriteLine(request.Credentials.ToString());

                request.Credentials = new NetworkCredential(ftpInfo.Username, ftpInfo.Password);
                request.Method = WebRequestMethods.Ftp.UploadFile;
                request.UsePassive = passive;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                //Console.WriteLine(response.StatusDescription);
                // request.UseBinary is true by default

                using (StreamReader reader = new StreamReader(path))
                {
                    using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
                    {
                        string content = reader.ReadToEnd();
                        writer.Write(content);
                        writer.Flush();
                    }
                }
            }
        }

        public static void DoDownload(FtpLogin ftpInfo, Uri source, string path, bool passive)
        {
            if (source.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(source) as FtpWebRequest;

                request.Credentials = new NetworkCredential(ftpInfo.Username, ftpInfo.Password);
                request.Method = WebRequestMethods.Ftp.DownloadFile;
                request.UsePassive = passive;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                FileStream outputStream = new FileStream(path, FileMode.Create);
                Stream ftpStream = response.GetResponseStream();
                int bufferSize = 2048; 
                int readCount; 
                byte[] buffer = new byte[bufferSize]; 
                readCount = ftpStream.Read(buffer, 0, bufferSize); 
                while (readCount > 0) 
                { 
                    outputStream.Write(buffer, 0, readCount); 
                    readCount = ftpStream.Read(buffer, 0, bufferSize); 
                } 
                ftpStream.Close(); 
                outputStream.Close(); 
                response.Close();
            }
        }

        /// <summary>
        /// Determine si le serveur FTP est actif ou passif
        /// </summary>
        /// <param name="ftpInfo"></param>
        /// <param name="directory"></param>
        /// <returns></returns>
        public static bool PassiveMode(FtpLogin ftpInfo, Uri directory)
        {
            FtpWebRequest request = WebRequest.Create(directory) as FtpWebRequest;
            FtpWebResponse response = null;

            request.Credentials = new NetworkCredential(ftpInfo.Username, ftpInfo.Password);
            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;

            try
            {
                response = request.GetResponse() as FtpWebResponse;
                return true;
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("227 Entering Passive Mode"))
                    return false;
                else
                    return true;
            }
        }

        /// <summary>
        /// Gets the file list of the specified directory
        /// Récupère la liste des fichier/répertoire du répertoire spécifiée en paramètre.
        /// </summary>
        /// <param name="ftpInfo"></param>
        /// <param name="directory"></param>
        /// <returns>La liste de fichier, ou null si une erreur surviens</returns>
        public static IList<FtpFile> DoListDirectory(FtpLogin ftpInfo, Uri directory, bool passive)
        {
            IList<FtpFile> listftp = new List<FtpFile>();
            if (directory.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(directory) as FtpWebRequest;
                FtpWebResponse response = null;

                request.Credentials = new NetworkCredential(ftpInfo.Username, ftpInfo.Password);
                request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
                request.UsePassive = passive;
                response = request.GetResponse() as FtpWebResponse;
                
                string textResponse = StringUtils.ReadContent(response.GetResponseStream());
                string[] directoriesNames = StringUtils.ParseLines(textResponse);

                foreach (string line in directoriesNames)
                {
                    FtpFile tmp;

                    tmp = ParseDetails(line);
                    tmp.FullName = directory.LocalPath + tmp.Name;

                    if ((tmp.Name != ".") && (tmp.Name != ".."))
                        listftp.Add(tmp);
                }
            }
            SortFtpFiles(listftp as List<FtpFile>);
            return listftp;
        }

        public static IList<FtpFile> DoListDirectory(FtpLogin ftpLogin, Uri directory, ListDirectoryKind kind, bool passive)
        {
            IList<FtpFile> listftp = DoListDirectory(ftpLogin, directory, passive);
            IList<FtpFile> list = new List<FtpFile>();

            foreach (FtpFile line in listftp)
            {
                switch (kind)
                {
                    case ListDirectoryKind.DIRECTORY_ONLY:
                        if (line.Directory == true)
                            list.Add(line);
                        break;
                    case ListDirectoryKind.FILE_ONLY:
                        if (line.Directory == false)
                            list.Add(line);
                        break;
                    case ListDirectoryKind.FILE_AND_DIRECTORY:
                        list.Add(line);
                        break;
                }
            }
            return list;
        }

        public static FtpFile ParseDetails(string line)
        {
            FtpFile file = new FtpFile();
            Match match = null;

            List<Regex> listregex = new List<Regex>();
            //serveur unix
            listregex.Add(new Regex(@"^(?<dir>[dl-])(?<permission>[rwx-]{9})\s+\d+\s\w*\s+\w*\s+(?<size>\d*)\s(?<timestamp>\w{3}\s+\d+\s+(?:\d*:?\d*)|(?:\d*))\s(?<name>[\.\w-]*)"));
            //serveur windows
            listregex.Add(new Regex(@"^(?<timestamp>\d{2}-\d{2}-\d{2}\s+\d{2}:?\d{2}\w{2})\s+(?<dir>(?:<DIR>)?)\s+(?<size>\d*)\s(?<name>[\.\w-]*)"));

            foreach (Regex regex in listregex)
            {
                match = regex.Match(line);
                if (match.Success)
                    break;
            }

            if (match.Success)
            {
                if ((match.Groups["dir"].Value == "-") || (match.Groups["dir"].Value == ""))
                {
                    file.Directory = false;
                    file.Length = long.Parse(match.Groups["size"].Value);
                }
                else
                    file.Directory = true;

                file.Droit = match.Groups["permission"].Value;
                file.LastAccessTime = match.Groups["timestamp"].Value;
                file.Name = match.Groups["name"].Value;

                return file;
            }
            else
            {
                file.Name = line;

                return file;
            }
        }

        public static void DoDeleteFile(FtpLogin ftpLogin, Uri uri, bool passive)
        {
            if (uri.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(uri) as FtpWebRequest;

                request.Credentials = new NetworkCredential(ftpLogin.Username, ftpLogin.Password);
                request.KeepAlive = false;
                request.UsePassive = passive;
                request.Method = WebRequestMethods.Ftp.DeleteFile;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string textResponse = reader.ReadToEnd();
                }

                response.Close();
            }
        }

        public static void DoDeleteDirectory(FtpLogin ftpLogin, Uri uri, bool passive)
        {
            if (uri.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(uri) as FtpWebRequest;

                request.Credentials = new NetworkCredential(ftpLogin.Username, ftpLogin.Password);
                request.KeepAlive = false;
                request.UsePassive = passive;
                request.Method = WebRequestMethods.Ftp.RemoveDirectory;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string textResponse = reader.ReadToEnd();
                }

                response.Close();
            }
        }

        public static void DoMakeDirectory(FtpLogin ftpLogin, Uri uri, bool passive)
        {
            if (uri.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(uri) as FtpWebRequest;

                request.Credentials = new NetworkCredential(ftpLogin.Username, ftpLogin.Password);
                request.Method = WebRequestMethods.Ftp.MakeDirectory;
                request.UseBinary = true;
                request.UsePassive = passive;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string textResponse = reader.ReadToEnd();
                }

                response.Close();
            }
        }

        public static void DoRenameFile(FtpLogin ftpLogin, Uri uri, string newname, bool passive)
        {
            if (uri.Scheme == Uri.UriSchemeFtp)
            {
                FtpWebRequest request = WebRequest.Create(uri) as FtpWebRequest;

                request.Credentials = new NetworkCredential(ftpLogin.Username, ftpLogin.Password);
                request.Method = WebRequestMethods.Ftp.Rename;
                request.UseBinary = true;
                request.RenameTo = newname;
                request.UsePassive = passive;
                FtpWebResponse response = request.GetResponse() as FtpWebResponse;

                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string textResponse = reader.ReadToEnd();
                }

                response.Close();
            }
        }

        public static void SortFtpFiles(List<FtpFile> list)
        {
            list.Sort(new FtpFile.FtpFileComparer());
        }
    }
}
